// Sample dataset for demo; replace with real data source later
const PAPERS = [
  { id: 'p1', title: 'Graph Attention Networks Revisited', year: 2025, month: '09', source: 'arXiv', category: 'GNN', subcategory: 'Attention', tags: ['gnn', 'attention', 'arxiv', 'nips24'], highlights: [
    '重新审视 GAT 在大规模图上的表达与效率瓶颈',
    '提出更稳定的注意力归一化与稀疏化策略',
    '在开放基准上显著提升收敛速度与泛化'] },
  { id: 'p2', title: 'Scalable Message Passing on Billion-Scale Graphs', year: 2025, month: '06', source: 'OSDI 25', category: 'GNN', subcategory: 'Scalability', tags: ['osdi25', 'systems', 'gnn'], highlights: [
    '工程化的消息传递 pipeline，支持十亿规模图',
    '分层采样与通信压缩降低训练成本',
    '在真实工业图上验证吞吐与准确性'] },
  { id: 'p3', title: 'Contrastive Graph Learning in the Wild', year: 2024, month: '12', source: 'NeurIPS 24', category: 'GNN', subcategory: 'Contrastive', tags: ['nips24', 'contrastive', 'gnn'], highlights: [
    '非受控场景下的对比式图学习设定',
    '鲁棒增广与负样本采样策略',
    '跨数据集的一致收益'] },
  { id: 'p4', title: 'Vision Transformers for Dense Prediction', year: 2025, month: '03', source: 'CVPR 25', category: 'CV', subcategory: 'Segmentation', tags: ['cvpr25', 'vit', 'segmentation'], highlights: [
    '将 ViT 架构系统化应用于密集预测',
    '多尺度特征融合与解码器设计',
    '在语义/实例分割上刷新 SOTA'] },
  { id: 'p5', title: 'Diffusion Models for Image Generation: A Survey', year: 2024, month: '11', source: 'arXiv', category: 'CV', subcategory: 'Generation', tags: ['arxiv', 'diffusion', 'survey'], highlights: [
    '扩散生成模型综述：训练、推理与评估',
    '对比 GAN/Flow 的优劣与适用性',
    '开放挑战与未来方向'] },
  { id: 'p6', title: 'Efficient Object Detection with Multiscale Features', year: 2025, month: '05', source: 'ATC 25', category: 'CV', subcategory: 'Detection', tags: ['atc25', 'detection'], highlights: [
    '多尺度特征的高效融合与调度',
    '在边缘设备上实现更低延迟',
    '工业级数据集的消融验证'] },
  { id: 'p7', title: 'Long-Sequence Modeling with Recurrent Memory', year: 2025, month: '08', source: 'ICLR 25', category: 'RNN', subcategory: 'Memory', tags: ['iclr25', 'memory', 'rnn'], highlights: [
    '面向长序列的显式记忆机制',
    '分段训练与记忆压缩保持长期依赖',
    '在时间预测与语言建模上提升'] },
  { id: 'p8', title: 'Interpretable RNNs for Time Series Forecasting', year: 2024, month: '10', source: 'arXiv', category: 'RNN', subcategory: 'Forecasting', tags: ['arxiv', 'forecast', 'rnn'] },
  { id: 'p9', title: 'Hybrid RNN-Transformer for Sequence Modeling', year: 2025, month: '02', source: 'NeurIPS 24', category: 'RNN', subcategory: 'Hybrid', tags: ['nips24', 'hybrid', 'sequence'] },
  { id: 'p10', title: 'Graph Sampling at Scale', year: 2025, month: '01', source: 'ATC 25', category: 'GNN', subcategory: 'Sampling', tags: ['atc25', 'gnn', 'sampling'] },
  { id: 'p11', title: 'Multi-Modal Vision-Language Segmentation', year: 2025, month: '07', source: 'ECCV 25', category: 'CV', subcategory: 'Segmentation', tags: ['eccv25', 'multimodal', 'segmentation'] },
  { id: 'p12', title: 'Temporal Graph Networks for Streams', year: 2024, month: '09', source: 'arXiv', category: 'GNN', subcategory: 'Temporal', tags: ['arxiv', 'temporal', 'gnn'] },
  { id: 'p13', title: 'Robust Time Series Forecasting with GRU', year: 2025, month: '04', source: 'KDD 25', category: 'RNN', subcategory: 'Forecasting', tags: ['kdd25', 'forecast', 'rnn'] },
  { id: 'p14', title: 'Self-Supervised Detection Pretraining', year: 2025, month: '03', source: 'CVPR 25', category: 'CV', subcategory: 'Detection', tags: ['cvpr25', 'self-supervised', 'detection'] },
  { id: 'p15', title: 'Graph Contrastive Pretraining', year: 2025, month: '05', source: 'ICML 25', category: 'GNN', subcategory: 'Contrastive', tags: ['icml25', 'contrastive', 'gnn'] },
  { id: 'p16', title: 'Recurrent Mixers for Long Context', year: 2025, month: '06', source: 'arXiv', category: 'RNN', subcategory: 'Memory', tags: ['arxiv', 'memory', 'rnn'] },
  { id: 'p17', title: 'Graph Transformers: A Survey', year: 2024, month: '12', source: 'arXiv', category: 'GNN', subcategory: 'Transformers', tags: ['arxiv', 'survey', 'gnn'] },
  { id: 'p18', title: 'Foundations of Diffusion-Based Perception', year: 2025, month: '07', source: 'ICCV 25', category: 'CV', subcategory: 'Generation', tags: ['iccv25', 'diffusion'] },
  { id: 'p19', title: 'Streaming GNN with Approximate Computations', year: 2025, month: '09', source: 'OSDI 25', category: 'GNN', subcategory: 'Scalability', tags: ['osdi25', 'streaming', 'gnn'] },
  { id: 'p20', title: 'Neural ODEs for Time Series', year: 2024, month: '08', source: 'NeurIPS 24', category: 'RNN', subcategory: 'Modeling', tags: ['nips24', 'ode', 'rnn'] },
];

const STATE = {
  selectedCategory: null, // 'GNN' | 'CV' | 'RNN' | null
  selectedSubcategory: null, // string | null
  selectedTags: new Set(), // AND semantics
};

// Utilities
const by = (sel, root = document) => root.querySelector(sel);
const all = (sel, root = document) => Array.from(root.querySelectorAll(sel));
const fmt = new Intl.NumberFormat('zh-CN');

function unique(arr) { return [...new Set(arr)]; }

function getAllCategories(data) {
  const grouped = data.reduce((acc, p) => {
    acc[p.category] ??= {};
    acc[p.category][p.subcategory] = (acc[p.category][p.subcategory] || 0) + 1;
    return acc;
  }, {});
  return grouped; // { GNN: {Sub: count, ...}, ... }
}

function getTagStats(data) {
  const map = new Map();
  for (const p of data) {
    for (const t of p.tags) map.set(t, (map.get(t) || 0) + 1);
  }
  return map; // Map<tag, count>
}

function applyFilters(data) {
  const { selectedCategory, selectedSubcategory, selectedTags } = STATE;
  return data.filter(p => {
    if (selectedCategory && p.category !== selectedCategory) return false;
    if (selectedSubcategory && p.subcategory !== selectedSubcategory) return false;
    if (selectedTags.size > 0) {
      for (const t of selectedTags) if (!p.tags.includes(t)) return false; // AND semantics
    }
    return true;
  });
}

function renderStats(filtered, availableTags) {
  by('#stat-total').textContent = fmt.format(filtered.length);
  const catCount = unique(filtered.map(p => p.category)).length;
  by('#stat-cats').textContent = fmt.format(catCount);
  by('#stat-tags').textContent = fmt.format(availableTags.size);
}

function renderCategoryTree(baseData, filteredGivenTags) {
  const tree = by('#category-tree');
  tree.innerHTML = '';

  const groupedAll = getAllCategories(baseData);
  const groupedFiltered = getAllCategories(filteredGivenTags); // counts respecting current tags

  for (const cat of Object.keys(groupedAll)) {
    const node = document.createElement('div');
    node.className = 'node';

    const head = document.createElement('div');
    head.className = 'node-head' + (STATE.selectedCategory === cat ? ' active' : '');
    head.innerHTML = `<span>${cat}</span><span class="count-badge">${fmt.format((groupedFiltered[cat] && Object.values(groupedFiltered[cat]).reduce((a,b)=>a+b,0)) || 0)}</span>`;
    head.addEventListener('click', () => {
      STATE.selectedCategory = (STATE.selectedCategory === cat) ? null : cat;
      STATE.selectedSubcategory = null;
      update();
    });

    const childrenWrap = document.createElement('div');
    childrenWrap.className = 'children';

    const subAll = groupedAll[cat];
    const subFiltered = groupedFiltered[cat] || {};

    for (const sub of Object.keys(subAll).sort()) {
      const leaf = document.createElement('div');
      const active = STATE.selectedCategory === cat && STATE.selectedSubcategory === sub;
      leaf.className = 'leaf' + (active ? ' active' : '');
      leaf.innerHTML = `<span>${sub}</span><span class="count-badge">${fmt.format(subFiltered[sub] || 0)}</span>`;
      leaf.addEventListener('click', () => {
        STATE.selectedCategory = cat;
        STATE.selectedSubcategory = (STATE.selectedSubcategory === sub) ? null : sub;
        update();
      });
      childrenWrap.appendChild(leaf);
    }

    node.appendChild(head);
    node.appendChild(childrenWrap);
    tree.appendChild(node);
  }
}

function renderTags(availableTags) {
  const bar = by('#tags-bar');
  bar.innerHTML = '';
  by('#tags-count').textContent = fmt.format(availableTags.size);

  [...availableTags.entries()].sort((a,b)=> b[1]-a[1]).forEach(([tag, count]) => {
    const chip = document.createElement('button');
    chip.className = 'chip' + (STATE.selectedTags.has(tag) ? ' active' : '');
    chip.textContent = `${tag} · ${count}`;
    chip.addEventListener('click', () => {
      if (STATE.selectedTags.has(tag)) STATE.selectedTags.delete(tag); else STATE.selectedTags.add(tag);
      update();
    });
    bar.appendChild(chip);
  });
}

function renderHotTags(availableTags) {
  const hot = by('#hot-tags');
  hot.innerHTML = '';
  const top = [...availableTags.entries()].sort((a,b)=> b[1]-a[1]).slice(0, 10);
  by('#hot-tags-meta').textContent = top.length ? `Top ${top.length}` : '';
  top.forEach(([tag, count]) => {
    const chip = document.createElement('button');
    chip.className = 'chip';
    chip.textContent = `${tag} · ${count}`;
    chip.addEventListener('click', () => {
      STATE.selectedTags.add(tag);
      update();
    });
    hot.appendChild(chip);
  });
}

function renderList(items) {
  const list = by('#paper-list');
  list.setAttribute('aria-busy', 'true');
  list.innerHTML = '';

  if (items.length === 0) {
    const empty = document.createElement('div');
    empty.className = 'paper-card';
    empty.innerHTML = '<p class="paper-title">没有符合条件的论文</p><p class="paper-meta">试着放宽筛选条件或清空标签</p>';
    list.appendChild(empty);
    list.setAttribute('aria-busy', 'false');
    return;
  }

  items
    .slice()
    .sort((a,b)=> {
      const ai = a.year * 100 + parseInt(a.month, 10);
      const bi = b.year * 100 + parseInt(b.month, 10);
      return bi - ai; // desc
    })
    .forEach(p => {
      const card = document.createElement('article');
      card.className = 'paper-card';
      const ym = `${p.year}-${p.month}`;
      card.innerHTML = `
        <h4 class="paper-title">${p.title}</h4>
        <div class="paper-meta"><span>${ym}</span><span>·</span><span>${p.source}</span><span>·</span><span>${p.category} / ${p.subcategory}</span></div>
        <div class="paper-tags">${p.tags.map(t=>`<span class=\"paper-tag\">${t}</span>`).join('')}</div>
        <div class="paper-actions"><button class="btn btn-ghost btn-xs preview-btn" data-id="${p.id}">预览概要</button></div>
      `;
      list.appendChild(card);
    });
  list.setAttribute('aria-busy', 'false');
  setupPreviewHandlers();
}

function resetCategory() {
  STATE.selectedCategory = null;
  STATE.selectedSubcategory = null;
}
function resetTags() {
  STATE.selectedTags.clear();
}

function update() {
  // First, filter data by selected tags & category
  const filteredByTags = applyFilters(PAPERS.map(x=>x)); // current filters include tags; but for tag menu when category changes, we need tags given the selected category only and not tag constraints? Add nuance below.

  // For tag availability calculation: only category/subcategory applied, not tags themselves
  const availableForTags = PAPERS.filter(p => {
    if (STATE.selectedCategory && p.category !== STATE.selectedCategory) return false;
    if (STATE.selectedSubcategory && p.subcategory !== STATE.selectedSubcategory) return false;
    return true;
  });

  const tagStats = getTagStats(availableForTags);

  renderCategoryTree(PAPERS, PAPERS.filter(p => {
    // counts in sidebar should respect current tag filter, but not category selection
    if (STATE.selectedTags.size > 0) {
      for (const t of STATE.selectedTags) if (!p.tags.includes(t)) return false;
    }
    return true;
  }));

  renderTags(tagStats);
  renderHotTags(tagStats);

  renderList(filteredByTags);
  renderStats(filteredByTags, tagStats);

  // Buttons state
  by('#btn-reset-tags').disabled = STATE.selectedTags.size === 0;
  const hasCategory = !!STATE.selectedCategory || !!STATE.selectedSubcategory;
  by('#btn-reset-category').disabled = !hasCategory;
}

window.addEventListener('DOMContentLoaded', () => {
  by('#btn-reset-tags').addEventListener('click', () => { resetTags(); update(); });
  by('#btn-reset-category').addEventListener('click', () => { resetCategory(); update(); });
  update();
});

// ============ Hover preview popover ============
let _popover, _hideTimer = null, _tracking = false;

function ensurePopover() {
  if (_popover) return _popover;
  const el = document.createElement('div');
  el.className = 'popover';
  el.setAttribute('role', 'dialog');
  el.setAttribute('aria-hidden', 'true');
  document.body.appendChild(el);
  el.addEventListener('mouseenter', () => clearTimeout(_hideTimer));
  el.addEventListener('mouseleave', () => scheduleHide());
  _popover = el;
  return _popover;
}

function buildPopoverHTML(paper) {
  const sourceBadge = (paper.source || '').toUpperCase();
  const items = (paper.highlights && paper.highlights.length)
    ? paper.highlights
    : [
        `问题：${paper.category} / ${paper.subcategory} 方向的代表性工作`,
        '方法：针对关键瓶颈给出简洁有效的改进',
        '表现：在公开基准取得稳定增益'
      ];
  return `
    <div class="pop-head">
      <div class="pop-title">概要</div>
      <div class="pop-source">${sourceBadge}</div>
    </div>
    <ul class="pop-list">${items.map(t => `<li>${t}</li>`).join('')}</ul>
  `;
}

function showPopoverFor(paper, x, y) {
  const pop = ensurePopover();
  pop.innerHTML = buildPopoverHTML(paper);
  pop.style.display = 'block';
  pop.setAttribute('aria-hidden', 'false');
  positionPopover(pop, x, y);
}

function positionPopover(pop, x, y) {
  const pad = 12; // gap from cursor
  const vw = window.innerWidth, vh = window.innerHeight;
  const rect = pop.getBoundingClientRect();
  let left = x + pad;
  let top = y + pad;
  if (left + rect.width + 8 > vw) left = Math.max(8, x - rect.width - pad);
  if (top + rect.height + 8 > vh) top = Math.max(8, y - rect.height - pad);
  pop.style.left = `${left}px`;
  pop.style.top = `${top}px`;
}

function scheduleHide(delay = 120) {
  clearTimeout(_hideTimer);
  _hideTimer = setTimeout(() => {
    if (_popover) {
      _popover.style.display = 'none';
      _popover.setAttribute('aria-hidden', 'true');
    }
    if (_tracking) {
      document.removeEventListener('mousemove', onMove);
      _tracking = false;
    }
  }, delay);
}

let _currentPaper = null;
let _lastPos = { x: 0, y: 0 };
function onMove(e) {
  _lastPos = { x: e.clientX, y: e.clientY };
  if (_popover && _popover.style.display === 'block') positionPopover(_popover, _lastPos.x, _lastPos.y);
}

let _scrollBound = false;
function setupPreviewHandlers() {
  const btns = all('.preview-btn');
  btns.forEach(btn => {
    const id = btn.getAttribute('data-id');
    const paper = PAPERS.find(p => p.id === id);
    if (!paper) return;

    btn.addEventListener('mouseenter', (e) => {
      clearTimeout(_hideTimer);
      _currentPaper = paper;
      showPopoverFor(paper, e.clientX, e.clientY);
      if (!_tracking) {
        document.addEventListener('mousemove', onMove);
        _tracking = true;
      }
    });
    btn.addEventListener('mousemove', (e) => {
      if (_currentPaper === paper && _popover && _popover.style.display === 'block') positionPopover(_popover, e.clientX, e.clientY);
    });
    btn.addEventListener('mouseleave', () => scheduleHide());
    btn.addEventListener('click', (e) => {
      // On click, toggle pin/unpin behavior: simple show near cursor
      e.preventDefault();
      clearTimeout(_hideTimer);
      _currentPaper = paper;
      const { clientX, clientY } = e;
      showPopoverFor(paper, clientX, clientY);
      if (!_tracking) {
        document.addEventListener('mousemove', onMove);
        _tracking = true;
      }
    });
    btn.addEventListener('focus', (e) => {
      const rect = btn.getBoundingClientRect();
      showPopoverFor(paper, rect.right, rect.top);
    });
    btn.addEventListener('blur', () => scheduleHide());
  });

  // Hide on scroll to avoid stale positions
  if (!_scrollBound) {
    window.addEventListener('scroll', () => scheduleHide(0), { passive: true });
    _scrollBound = true;
  }
}
